#ifndef COW_HPP_INCLUDED
#define COW_HPP_INCLUDED

/*
 * cow.hpp
 * This file is part of dynad
 *
 * Copyright (C) 2014 - Sebastian Gesemann
 *
 * dynad is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * dynad is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with dynad. If not, see <http://www.gnu.org/licenses/>.
 */

/*
 * Polymorphic Copy-On-Write Wrapper
 */

#include <cassert>
#include <cstddef>
#include <algorithm>
#include <utility>
#include <memory>
#include <type_traits>

namespace cow_details {

struct shared_clonable
{
public:
	virtual std::shared_ptr<shared_clonable> clone() const = 0;

protected:
	shared_clonable()
	{}

	~shared_clonable()
	{}


private:
	shared_clonable(shared_clonable const&);
	shared_clonable& operator=(shared_clonable const&);
};

template <class T> struct cow_wrapper : shared_clonable
{
	T obj;

	template <class... Args>
	cow_wrapper(Args&&... args)
	    : obj(std::forward<Args>(args)...)
	{}


	std::shared_ptr<shared_clonable> clone() const
	{
		return std::make_shared<cow_wrapper>(obj);
	}

	static const std::shared_ptr<cow_wrapper>& default_constructed();
};

template <class T>
const std::shared_ptr<cow_wrapper<T> >& cow_wrapper<T>::default_constructed()
{
	static auto dc = std::make_shared<cow_wrapper<T> >();
	return dc;
}

} // namespace cow_details

enum cow_no_init_tag {
	cow_no_init
};

enum cow_emplace_tag {
	cow_emplace
};

template <class T> class cow
{
	template <class> friend class cow;

public:
	/// construct cow object not to contain any valid value
	cow(cow_no_init_tag) : scp(0), ptr(0)
	{}


	/// construct cow with default-constructed T value
	cow()
	{
		auto tmp = cow_details::cow_wrapper<T>::default_constructed();
		this->ptr = &tmp->obj;
		this->scp = std::move(tmp);
	}

	/// conversion constructor U -> T
	template <class U>
	cow(U&& x,
	    typename std::enable_if<std::is_convertible<U, T>::value>::type* = 0)
	{
		auto tmp =
		    std::make_shared<cow_details::cow_wrapper<T> >(std::forward<U>(x));
		this->ptr = &tmp->obj;
		this->scp = std::move(tmp);
	}

	/// emplace constructor
	template <class... Args> cow(cow_emplace_tag, Args&&... args)
	{
		auto tmp = std::make_shared<cow_details::cow_wrapper<T> >(
		    std::forward<Args>(args)...);
		this->ptr = &tmp->obj;
		this->scp = std::move(tmp);
	}

	/// copy constructor
	cow(cow<T> const& x) : scp(x.scp), ptr(x.ptr)
	{}


	/// move constructor
	cow(cow<T>&& x) : scp(std::move(x.scp)), ptr(x.ptr)
	{
		x.ptr = 0;
	}

	/// copying conversion constructor cow<U> -> cow<T>
	template <class U>
	cow(cow<U> const& x,
	    typename std::enable_if<std::is_convertible<U*, T*>::value>::type* = 0)
	    : scp(x.scp), ptr(x.ptr)
	{}


	/// moving conversion constructor cow<U> -> cow<T>
	template <class U>
	cow(cow<U>&& x,
	    typename std::enable_if<std::is_convertible<U*, T*>::value>::type* = 0)
	    : scp(std::move(x.scp)), ptr(x.ptr)
	{
		x.ptr = 0;
	}

	/// swap two cow instances
	void swap(cow& that)
	{
		using std::swap;
		swap(this->scp, that.scp);
		swap(this->ptr, that.ptr);
	}

	/// swap two cow instances
	friend void swap(cow& a, cow& b)
	{
		a->swap(b);
	}

	/// unified assignment operator
	cow& operator=(cow x)
	{
		swap(x);
		return *this;
	}

	/// get read access to contained object
	T const& read() const
	{
		assert(!!ptr);
		return *ptr;
	}

	/// get write access to contained object
	T& write()
	{
		assert(!!ptr);
		if (!scp.unique())
			uniqify();
		return *ptr;
	}

	bool is_valid() const
	{
		return !!ptr;
	}
	bool operator!() const
	{
		return !ptr;
	}

private:
	std::shared_ptr<cow_details::shared_clonable> scp;
	T* ptr;

	void uniqify();
};

template <class T> void cow<T>::uniqify()
{
	auto cop = scp->clone();
	const std::ptrdiff_t offset = (char*)ptr - (char*)(scp.get());
	ptr = (T*)((char*)(cop.get()) + offset);
	scp = std::move(cop);
}

#endif

